/*
*/

#include <GLUnity.h>
#include "FBXFile.h"
#include "Matrixf.h"
#include "Vector3f.h"

#define M_PI    3.1415926
CBVHParser g_BVHParser;

// 
static float   camera_yaw = 0.0f;      // 
static float   camera_pitch = -20.0f;  // 
static float   camera_distance = 5.0f; // 

// 
static int     drag_mouse_r = 0; // 
static int     drag_mouse_l = 0; // ?
static int     drag_mouse_m = 0; // 
static int     last_mouse_x, last_mouse_y; //

int    g_dwFrameIndex=-1;
static bool g_bPlay = false;

#define ANGLE(f) (((f)*M_PI)/180.0)//(f)  //(((f)*180.0)/M_PI)//(((f)*M_PI)/180.0)

void    RenderJoint (   const CBVHParser::CBVHJoint     *   pBVHJoint,
                        const CBVHParser::PCBVH_FRAME       pBVHFrame,
                        const Matrixf                   &   trans   )
{
    ::glPushMatrix();

    if(pBVHJoint->HasSite()){
        ::glColor3f(0.0f, 0.0f, 1.0f);
    }
    else{
        ::glColor3f(0.0f, 1.0f, 0.0f);
    }
    Vector3f vOffset(pBVHJoint->GetOffset()[0], pBVHJoint->GetOffset()[1], pBVHJoint->GetOffset()[2]);

    ///
    if(NULL != pBVHFrame){
        const CBVHParser::BVH_FRAME_SEGMENT& bvhFrameSegment = pBVHFrame->vSegments[pBVHJoint->GetJointIndex()];
        for(int j=0,cc=pBVHJoint->GetChannelsCount();j<cc;j++){
            switch (pBVHJoint->GetChannel(j))
            {
            case CBVHParser::Xposition:
                vOffset[0]=bvhFrameSegment[CBVHParser::Xposition];
                break;
            case CBVHParser::Yposition:
                vOffset[1]=bvhFrameSegment[CBVHParser::Yposition];
                break;
            case CBVHParser::Zposition:
                vOffset[2]=bvhFrameSegment[CBVHParser::Zposition];
                break;
            default:
                break;
            }
        }
    }
    vOffset=trans.preMult(vOffset);
    ::glBegin(GL_LINES);
    ::glVertex3f(0.0f, 0.0f, 0.0f);
    ::glVertex3f(vOffset.x(), vOffset.y(), vOffset.z());
    ::glEnd();

    ::glPointSize(10.0f);
    ::glColor3f(1.0f, 0.0f, 0.0f);
    ::glBegin(GL_POINTS);
    ::glVertex3f(vOffset.x(), vOffset.y(), vOffset.z());
    ::glEnd();

    ::glTranslatef(vOffset.x(), vOffset.y(), vOffset.z());

    if(pBVHJoint->HasSite()){
        ::glColor3f(1.0f, 1.0f, 0.0f);
        Vector3f vSiteOffset(pBVHJoint->GetSiteOffset()[0], pBVHJoint->GetSiteOffset()[1], pBVHJoint->GetSiteOffset()[2]);
        vSiteOffset=trans.preMult(vSiteOffset);
        ::glBegin(GL_LINES);
        ::glVertex3f(0.0f, 0.0f, 0.0f);
        ::glVertex3f(vSiteOffset.x(), vSiteOffset.y(), vSiteOffset.z());
        ::glEnd();
    }
    
    Matrixf subTrans;
    if(NULL != pBVHFrame){
        const CBVHParser::BVH_FRAME_SEGMENT& bvhFrameSegment = pBVHFrame->vSegments[pBVHJoint->GetJointIndex()];
        for(int j=0,cc=pBVHJoint->GetChannelsCount();j<cc;j++){
            switch (pBVHJoint->GetChannel(j))
            {
            case CBVHParser::Xrotation:
                subTrans.preMult(Matrixf::rotate(ANGLE(bvhFrameSegment[CBVHParser::Xrotation]), 1.0f, 0.0f, 0.0f));
                break;
            case CBVHParser::Yrotation:
                subTrans.preMult(Matrixf::rotate(ANGLE(bvhFrameSegment[CBVHParser::Yrotation]), 0.0f, 1.0f, 0.0f));
                break;
            case CBVHParser::Zrotation:
                subTrans.preMult(Matrixf::rotate(ANGLE(bvhFrameSegment[CBVHParser::Zrotation]), 0.0f, 0.0f, 1.0f));
                break;
            default:
                break;
            }
        }
    }
    ::glMultMatrixf(subTrans.ptr());
    
    for(int i=0,c=pBVHJoint->GetChildrenCount();i<c;i++){
        RenderJoint(pBVHJoint->GetChild(i), pBVHFrame, trans);
    }
    ::glPopMatrix();
}

void    FrameRender (   int nFrame  )
{
    
    ::glColor3f(0.0f, 1.0f, 0.0f);
    const CBVHParser::CBVHJoint * pBVHJoint = g_BVHParser.GetRoot();
    Matrixf mat;
    if(     nFrame>0
        ||  nFrame<g_BVHParser.GetFrameCount()){
        const CBVHParser::PCBVH_FRAME pBVHFrame = g_BVHParser.GetFrame(nFrame);
        RenderJoint(pBVHJoint, pBVHFrame, mat);
    }
    else{
        RenderJoint(pBVHJoint, NULL, mat);
    }
}

void    displayProc (   void    )
{
    ::glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    ::glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    ::glMatrixMode(GL_MODELVIEW);
    ::glLoadIdentity();

    

    //
    ::glTranslatef(0.0f, -10.0f, -50.0f);
    glTranslatef( 0.0, 0.0, - camera_distance );
    glRotatef( - camera_pitch, 1.0, 0.0, 0.0 );
    glRotatef( - camera_yaw, 0.0, 1.0, 0.0 );

    ::glColor3f(0.5f, 0.5f, 0.5f);
    ::glBegin(GL_LINES);
    for (int i=-100;i<100;i++)
    {
        ::glVertex3f(-20000.0f, 0.0f, i*-10.0f);
        ::glVertex3f(20000.0f, 0.0f, i*-10.0f);

        ::glVertex3f(i*-10.0f, 0.0f, -20000.0f);
        ::glVertex3f(i*-10.0f, 0.0f, 20000.0f);
    }

    ::glEnd();

    FrameRender(g_dwFrameIndex);

    //

    ::glFlush();
    ::glutSwapBuffers();
}

void    reshapeProc (   int w, 
                        int h    )
{
    ::glViewport(0, 0, w, h);
    ::glMatrixMode(GL_PROJECTION);
    ::glLoadIdentity();
    ::gluPerspective(60.0f, (GLdouble)w/(GLdouble)h , 0.1f, 1000.0f);
}

void timerProc(int value)
{
    if(g_bPlay){
        if(++g_dwFrameIndex>=g_BVHParser.GetFrameCount()){
            g_dwFrameIndex=0;
        }
        ::glutPostRedisplay();
        ::glutTimerFunc(2, timerProc, 10);
    }
}

void mouseProc(int button, int state, int x, int y)
{
    if ( ( button == GLUT_LEFT_BUTTON ) && ( state == GLUT_DOWN ) )
        drag_mouse_l = 1;
    else if ( ( button == GLUT_LEFT_BUTTON ) && ( state == GLUT_UP ) )
        drag_mouse_l = 0;
    if ( ( button == GLUT_RIGHT_BUTTON ) && ( state == GLUT_DOWN ) )
        drag_mouse_r = 1;
    else if ( ( button == GLUT_RIGHT_BUTTON ) && ( state == GLUT_UP ) )
        drag_mouse_r = 0;

    if ( ( button == GLUT_MIDDLE_BUTTON ) && ( state == GLUT_DOWN ) )
        drag_mouse_m = 1;
    else if ( ( button == GLUT_MIDDLE_BUTTON ) && ( state == GLUT_UP ) )
        drag_mouse_m = 0;

    ::glutPostRedisplay();

    last_mouse_x = x;
    last_mouse_y = y;
}

void motionProc(int x, int y)
{
    if ( drag_mouse_r )
    {
        camera_yaw -= ( x - last_mouse_x ) * 1.0;
        if ( camera_yaw < 0.0 )
            camera_yaw += 360.0;
        else if ( camera_yaw > 360.0 )
            camera_yaw -= 360.0;

        camera_pitch -= ( y - last_mouse_y ) * 1.0;
        if ( camera_pitch < -90.0 )
            camera_pitch = -90.0;
        else if ( camera_pitch > 90.0 )
            camera_pitch = 90.0;
    }
    if ( drag_mouse_l )
    {
        camera_distance += ( y - last_mouse_y ) * 0.2;
        if ( camera_distance < 2.0 )
            camera_distance = 2.0;
    }

    last_mouse_x = x;
    last_mouse_y = y;

    ::glutPostRedisplay();
}

void keyboardProc(unsigned char key, int x, int y)
{
    switch (key)
    {
    case ' ':
        if(g_bPlay){
            g_bPlay = false;
        }
        else{
            g_bPlay = true;
            ::glutTimerFunc(2, timerProc, 10);
        }
        break;
    default:
        break;
    }
}

int main(int argc, char** argv)
{
    ::glutInit(&argc, argv);
    ::glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
    ::glutInitWindowSize(800, 600);
    ::glutCreateWindow("BVH Player");
    if(GLEW_OK==::glewInit()){
        g_BVHParser.ParseFile(argv[1]);
        ::glEnable(GL_DEPTH);
        ::glutDisplayFunc(displayProc);
        ::glutReshapeFunc(reshapeProc);
        ::glutMotionFunc(::motionProc);
        ::glutMouseFunc(::mouseProc);
        ::glutKeyboardFunc(keyboardProc);
//        ::glutTimerFunc(2, timerProc, 10);
        ::glutMainLoop();
        return 0;
    }
    else{
        return -1;
    }
}